package cn.longwingstech.intelligence.bibackend.controller;

import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONUtil;
import cn.longwingstech.intelligence.bibackend.ai.PromptTemplates;
import cn.longwingstech.intelligence.bibackend.ai.entity.GenResultEntity;
import cn.longwingstech.intelligence.bibackend.ai.entity.ImageResultEntity;
import cn.longwingstech.intelligence.bibackend.aop.RateLimiter;
import cn.longwingstech.intelligence.bibackend.common.BaseResponse;
import cn.longwingstech.intelligence.bibackend.common.DeleteRequest;
import cn.longwingstech.intelligence.bibackend.common.ResultUtils;
import cn.longwingstech.intelligence.bibackend.exception.BusinessException;
import cn.longwingstech.intelligence.bibackend.exception.ErrorCode;
import cn.longwingstech.intelligence.bibackend.exception.ThrowUtils;
import cn.longwingstech.intelligence.bibackend.model.dto.chart.*;
import cn.longwingstech.intelligence.bibackend.model.entity.Chart;
import cn.longwingstech.intelligence.bibackend.model.entity.User;
import cn.longwingstech.intelligence.bibackend.model.enums.ChartStatus;
import cn.longwingstech.intelligence.bibackend.model.vo.chart.BiResponse;
import cn.longwingstech.intelligence.bibackend.model.vo.chart.ChartVO;
import cn.longwingstech.intelligence.bibackend.mq.MqEnums;
import cn.longwingstech.intelligence.bibackend.service.ChartService;
import cn.longwingstech.intelligence.bibackend.service.UserService;
import cn.longwingstech.intelligence.bibackend.utils.ExcelUtils;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.template.st.StTemplateRenderer;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.Assert;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 帖子接口
 */
@RestController
@RequestMapping("/chart")
@Slf4j
@Tag(name = "图表接口")
@SaCheckLogin
public class ChartController {

    @Resource
    private ChartService chartService;

    @Resource
    private UserService userService;

    @Resource
    @Qualifier("chatClient")
    private ChatClient chatClient;


    @Resource
    private RabbitTemplate rabbitTemplate;
    /**
     * 多模态
     */
    @Resource
    @Qualifier("multimodalChatClient")
    private ChatClient multimodalChatClient;

    /**
     * 创建
     *
     * @param chartAddRequest
     * @return
     */
    @PostMapping("/add")
    @Operation(summary = "创建图表")
    public BaseResponse<Long> addChart(@RequestBody ChartAddRequest chartAddRequest) {
        if (chartAddRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Chart chart = new Chart();
        BeanUtils.copyProperties(chartAddRequest, chart);
        User loginUser = userService.getLoginUser();
        chart.setUserId(loginUser.getId());
        boolean result = chartService.save(chart);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        long newChartId = chart.getId();
        return ResultUtils.success(newChartId);
    }

    /**
     * 删除
     *
     * @param deleteRequest
     * @return
     */
    @PostMapping("/delete")
    @Operation(summary = "删除图表")
    public BaseResponse<Boolean> deleteChart(@RequestBody DeleteRequest deleteRequest) {
        if (deleteRequest == null || deleteRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User user = userService.getLoginUser();
        long id = deleteRequest.getId();
        // 判断是否存在
        Chart oldChart = chartService.getById(id);
        ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR);
        // 仅本人或管理员可删除
        Assert.state(user.getId().equals(oldChart.getUserId()), "无权限操作");
        boolean b = chartService.removeById(id);
        return ResultUtils.success(b);
    }

    /**
     * 更新（仅管理员）
     *
     * @param chartUpdateRequest
     * @return
     */
    @PostMapping("/update")
    @SaCheckRole("admin")
    @Operation(summary = "管理员更新图表")
    public BaseResponse<Boolean> updateChart(@RequestBody ChartUpdateRequest chartUpdateRequest) {
        if (chartUpdateRequest == null || chartUpdateRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Chart chart = new Chart();
        BeanUtils.copyProperties(chartUpdateRequest, chart);
        long id = chartUpdateRequest.getId();
        // 判断是否存在
        Chart oldChart = chartService.getById(id);
        ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR);
        boolean result = chartService.updateById(chart);
        return ResultUtils.success(result);
    }

    /**
     * 根据 id 获取
     *
     * @param id
     * @return
     */
    @GetMapping("/get")
    @Operation(summary = "根据ID获取图表")
    public BaseResponse<ChartVO> getChartById(long id) {
        if (id <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Chart chart = chartService.getById(id);
        ChartVO chartVO = new ChartVO();
        BeanUtils.copyProperties(chart, chartVO);
        chartVO.setGenChart(JSONUtil.parseObj(chart.getGenChart()));
        if (chart == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
        }
        return ResultUtils.success(chartVO);
    }

    /**
     * 分页获取列表（封装类）
     *
     * @param chartQueryRequest
     * @return
     */
    @PostMapping("/list/page")
    @Operation(summary = "分页获取图表")
    @SaCheckRole("admin")
    public BaseResponse<Page<Chart>> listChartByPage(@RequestBody ChartQueryRequest chartQueryRequest) {
        long current = chartQueryRequest.getCurrent();
        long size = chartQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
        Page<Chart> chartPage = chartService.page(Page.of(current, size));
        return ResultUtils.success(chartPage);
    }

    /**
     * 分页获取当前用户创建的资源列表
     *
     * @param chartQueryRequest
     * @return
     */
    @PostMapping("/my/list/page")
    @Operation(summary = "分页获取当前用户创建的图表")
    public BaseResponse<Page<ChartVO>> listMyChartByPage(@RequestBody ChartQueryRequest chartQueryRequest) {
        if (chartQueryRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User loginUser = userService.getLoginUser();
        chartQueryRequest.setUserId(loginUser.getId());
        long current = chartQueryRequest.getCurrent();
        long size = chartQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
        QueryWrapper queryWrapper = QueryWrapper.create()
                .eq(Chart::getUserId, loginUser.getId())
                .orderBy(Chart::getCreateTime, false);
        Page<Chart> chartPage = chartService.page(Page.of(current, size),queryWrapper);
        Page<ChartVO> result = Page.of(current, size);
        result.setTotalPage(chartPage.getTotalPage());

        List<ChartVO> listResult = chartPage.getRecords().stream().map(t -> {
            ChartVO vo = new ChartVO();
            BeanUtils.copyProperties(t, vo);
            vo.setGenChart(JSONUtil.parseObj(t.getGenChart()));
            return vo;
        }).toList();

        result.setRecords(listResult);
        return ResultUtils.success(result);
    }

    // endregion

    /**
     * 编辑（用户）
     *
     * @param chartEditRequest
     * @param request
     * @return
     */
    @PostMapping("/edit")
    @Operation(summary = "用户编辑图表")
    public BaseResponse<Boolean> editChart(@RequestBody ChartEditRequest chartEditRequest, HttpServletRequest request) {
        if (chartEditRequest == null || chartEditRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Chart chart = new Chart();
        BeanUtils.copyProperties(chartEditRequest, chart);
        User loginUser = userService.getLoginUser();
        long id = chartEditRequest.getId();
        // 判断是否存在
        Chart oldChart = chartService.getById(id);
        ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR);
        // 仅本人或管理员可编辑
        Assert.state(loginUser.getId().equals(oldChart.getUserId()), "无权限操作");
        boolean result = chartService.updateById(chart);
        return ResultUtils.success(result);
    }

    /**
     * 智能分析（同步）
     *
     * @param multipartFile
     * @param genChartByAiRequest
     * @return
     */
    @PostMapping("/gen")
    @Operation(summary = "智能分析（同步）")
    @RateLimiter(key = "genChartByAi", time = 60, count = 5)
    public BaseResponse<BiResponse> genChartByAi(@RequestPart("file") MultipartFile multipartFile,
                                                 GenChartByAiRequest genChartByAiRequest) {
        String name = genChartByAiRequest.getName();
        String goal = genChartByAiRequest.getGoal();
        String chartType = genChartByAiRequest.getChartType();
        // 校验
        ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标为空");
        ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");
        // 校验文件
        long size = multipartFile.getSize();
        String originalFilename = multipartFile.getOriginalFilename();
        // 校验文件大小
        final long ONE_MB = 1024 * 1024L;
        ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件超过 1M");
        String suffix = FileUtil.getSuffix(originalFilename);
        final List<String> validFileSuffixList = Arrays.asList("xlsx");
        ThrowUtils.throwIf(!validFileSuffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件后缀非法");
        // 获取图表
        String chartData = ExcelUtils.excelToCsv(multipartFile);
        User loginUser = userService.getLoginUser();
        // 开始分析
        PromptTemplate template = PromptTemplate.builder()
                .renderer(StTemplateRenderer.builder().startDelimiterToken('^').endDelimiterToken('^').build())
                .template(PromptTemplates.CHARTS_GEN)
                .build();
        String prompt = template.render(Map.of("name", name, "goal", goal, "chartData", chartData, "chartType", chartType));
        GenResultEntity entity = null;
        entity = chatClient.prompt(prompt).call().entity(GenResultEntity.class);
        //保存
        Chart chart = new Chart();
        chart.setName(name);
        chart.setGoal(goal);
        chart.setChartData(chartData);
        chart.setChartType(chartType);
        chart.setGenChart(entity.getOption());
        chart.setGenResult(entity.getResult());
        chart.setUserId(loginUser.getId());
        chart.setStatus(ChartStatus.SUCCEED.getValue());
        boolean result = chartService.save(chart);
        ThrowUtils.throwIf(!result, ErrorCode.SYSTEM_ERROR);
        BiResponse biResponse = new BiResponse();
        biResponse.setGenChart(JSONUtil.parseObj(entity.getOption()));
        biResponse.setGenResult(entity.getResult());
        biResponse.setChartId(chart.getId());
        return ResultUtils.success(biResponse);
    }

    /**
     * 智能分析（异步）
     *
     * @param multipartFile
     * @param genChartByAiRequest
     * @return
     */
    @PostMapping("/gen/async")
    @Operation(summary = "智能分析（异步）")
    @RateLimiter(key = "genChartByAi", time = 60, count = 5)
    public BaseResponse<BiResponse> genChartByAiAsync(@RequestPart("file") MultipartFile multipartFile,
                                                      GenChartByAiRequest genChartByAiRequest) {
        String name = genChartByAiRequest.getName();
        String goal = genChartByAiRequest.getGoal();
        String chartType = genChartByAiRequest.getChartType();
        // 校验
        ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标为空");
        ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");
        // 校验文件
        long size = multipartFile.getSize();
        String originalFilename = multipartFile.getOriginalFilename();
        // 校验文件大小
        final long ONE_MB = 1024 * 1024L;
        ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件超过 1M");
        // 校验文件后缀 aaa.png
        String suffix = FileUtil.getSuffix(originalFilename);
        final List<String> validFileSuffixList = Arrays.asList("xlsx", "xls");
        ThrowUtils.throwIf(!validFileSuffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件后缀非法");
// 获取图表
        String chartData = ExcelUtils.excelToCsv(multipartFile);
        User loginUser = userService.getLoginUser();

        // 插入到数据库
        Chart chart = new Chart();
        chart.setName(name);
        chart.setGoal(goal);
        chart.setChartData(chartData);
        chart.setChartType(chartType);
        chart.setStatus(ChartStatus.WAIT.getValue());
        chart.setUserId(loginUser.getId());
        boolean saveResult = chartService.save(chart);
        ThrowUtils.throwIf(!saveResult, ErrorCode.SYSTEM_ERROR, "图表保存失败");

        Thread.ofVirtual().name("gen-chart-async-" + System.currentTimeMillis()).start(() -> {
            Chart updateEntity = new Chart();
            updateEntity.setId(chart.getId());
            updateEntity.setStatus(ChartStatus.RUNNING.getValue());
            boolean uploadStats = chartService.updateById(updateEntity);
            Assert.state(uploadStats, "图表更新失败");
            // 开始分析
            PromptTemplate template = PromptTemplate.builder()
                    .renderer(StTemplateRenderer.builder().startDelimiterToken('^').endDelimiterToken('^').build())
                    .template(PromptTemplates.CHARTS_GEN)
                    .build();
            String prompt = template.render(Map.of("name", name, "goal", goal, "chartData", chartData, "chartType", chartType));
            System.err.println(prompt);
            GenResultEntity entity = null;
            try {
                entity = chatClient.prompt(prompt).call().entity(GenResultEntity.class);
            } catch (Exception e) {
                handleChartUpdateError(chart.getId(), "AI 错误");
            }
            //保存
            Chart chartAsync = new Chart();
            chartAsync.setName(name);
            chartAsync.setGoal(goal);
            chartAsync.setChartData(chartData);
            chartAsync.setChartType(chartType);
            chartAsync.setGenChart(entity.getOption());
            chartAsync.setGenResult(entity.getResult());
            chartAsync.setUserId(loginUser.getId());
            chartAsync.setStatus(ChartStatus.SUCCEED.getValue());
            chartAsync.setId(chart.getId());
            boolean result = chartService.updateById(chartAsync);
            ThrowUtils.throwIf(!result, ErrorCode.SYSTEM_ERROR);
        });

        BiResponse biResponse = new BiResponse();
        biResponse.setChartId(chart.getId());
        return ResultUtils.success(biResponse);
    }

    /**
     * 智能分析（异步消息队列）
     *
     * @param multipartFile
     * @param genChartByAiRequest
     * @return
     */
    @PostMapping("/gen/async/mq")
    @Operation(summary = "智能分析（异步消息队列）")
    @RateLimiter(key = "genChartByAi", time = 60, count = 5)
    public BaseResponse<BiResponse> genChartByAiAsyncMq(@RequestPart("file") MultipartFile multipartFile,
                                                        GenChartByAiRequest genChartByAiRequest) {
        String name = genChartByAiRequest.getName();
        String goal = genChartByAiRequest.getGoal();
        String chartType = genChartByAiRequest.getChartType();
        // 校验
        ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标为空");
        ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");
        // 校验文件
        long size = multipartFile.getSize();
        String originalFilename = multipartFile.getOriginalFilename();
        // 校验文件大小
        final long ONE_MB = 1024 * 1024L;
        ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件超过 1M");
        // 校验文件后缀 aaa.png
        String suffix = FileUtil.getSuffix(originalFilename);
        final List<String> validFileSuffixList = Arrays.asList("xlsx", "xls");
        ThrowUtils.throwIf(!validFileSuffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件后缀非法");

        // 获取图表
        String chartData = ExcelUtils.excelToCsv(multipartFile);
        User loginUser = userService.getLoginUser();

        // 插入到数据库
        Chart chart = new Chart();
        chart.setName(name);
        chart.setGoal(goal);
        chart.setChartData(chartData);
        chart.setChartType(chartType);
        chart.setStatus(ChartStatus.WAIT.getValue());
        chart.setUserId(loginUser.getId());
        boolean saveResult = chartService.save(chart);
        ThrowUtils.throwIf(!saveResult, ErrorCode.SYSTEM_ERROR, "图表保存失败");
        // 创建消息
        rabbitTemplate.convertAndSend(MqEnums.GEN.getExchange(), MqEnums.GEN.getRoutingKey(), JSONUtil.toJsonStr(chart));

        BiResponse biResponse = new BiResponse();
        biResponse.setChartId(chart.getId());
        return ResultUtils.success(biResponse);
    }

    /**
     * 图片分析
     */
    @PostMapping("/image")
    @Operation(summary = "图片分析")
    @RateLimiter(key = "genChartByAiImage", time = 60, count = 5)
    public BaseResponse<ImageResultEntity> genChartByAiImage(@RequestPart("file") MultipartFile file) {
        ImageResultEntity entity = multimodalChatClient.prompt()
                .user(t -> t.text("分析图片如下：").media(MimeTypeUtils.IMAGE_PNG, file.getResource()))
                .call().entity(ImageResultEntity.class);

        return ResultUtils.success(entity);

    }
    private void handleChartUpdateError(long chartId, String execMessage) {
        Chart updateChartResult = new Chart();
        updateChartResult.setId(chartId);
        updateChartResult.setStatus(ChartStatus.FAILED.getValue());
        updateChartResult.setExecMessage("execMessage");
        boolean updateResult = chartService.updateById(updateChartResult);
        if (!updateResult) {
            log.error("更新图表失败状态失败" + chartId + "," + execMessage);
        }
    }
}
